package ooo.reindeer.data.entity;

import com.fasterxml.jackson.annotation.JsonValue;
import ooo.reindeer.commons.utils.DateTimeUtil;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


public class Entity implements Map<String, Object> {

    @JsonValue
    private Map<String, Object> options = new ConcurrentHashMap<String, Object>();

    public <T> Entity set(Option<T> option, T value) {
        return set(option.name(), value);
    }

    @Override
    public int size() {
        return options.size();
    }

    @Override
    public boolean isEmpty() {
        return options.isEmpty();
    }

    @Override
    public boolean containsKey(Object o) {
        return options.containsKey(o);
    }

    @Override
    public boolean containsValue(Object o) {
        return options.containsValue(o);
    }

    @Override
    public Object get(Object o) {
        return options.get(o);
    }


    @Override
    public Object put(String s, Object o) {
        if (o == null) {
            return options.remove(s);
        }
        return options.put(s, o);
    }

    @Override
    public Object remove(Object o) {
        return options.remove(o);
    }

    @Override
    public void putAll(Map<? extends String, ?> map) {
        options.putAll(map);
    }

    @Override
    public void clear() {
        options.clear();
    }

    @Override
    public Set<String> keySet() {
        return options.keySet();
    }

    @Override
    public Collection<Object> values() {
        return options.values();
    }

    @Override
    public Set<Entry<String, Object>> entrySet() {
        return options.entrySet();
    }


    @Override
    public boolean equals(Object o) {
        return options.equals(o);
    }

    @Override
    public int hashCode() {
        return options.hashCode();
    }

    @Override
    public String toString() {
        return options.toString();
    }

    public <T> Entity set(String option, T value) {
        if (option == null) {
            throw new NullPointerException("option");
        } else {
            if (value == null) {
                this.options.remove(option);
            } else {
                this.options.put(option, value);
            }

            return this;
        }
    }

    public <T> T get(Option<T> option) {
        T value = getMultipleNameOptionValue(option);
        return (value!=null)?value:(T) get(option.name());
    }

    public  LocalDateTime getLocalDateTime(Option<LocalDateTime> option) {
        return get(option, (LocalDateTime)null);
    }
    public LocalDateTime get(Option<LocalDateTime> option, LocalDateTime defaultValue){
        return parseLocalDateTime(getMultipleNameOptionValue(option),get(option.name(),defaultValue));
    }
    public LocalDateTime get(String option, LocalDateTime defaultValue) {
        return parseLocalDateTime(get(option),defaultValue);
    }

    private LocalDateTime parseLocalDateTime(Object value,LocalDateTime defaultValue) {
        if (value == null) {
            return defaultValue;
        } else {
            if(value instanceof LocalDateTime){
                return (LocalDateTime)value;
            }else if(value instanceof String){
                return LocalDateTime.parse((String)value, DateTimeUtil.LOCAL_DATETIME);
            }else{
                return LocalDateTime.parse(value.toString(), DateTimeUtil.LOCAL_DATETIME);
            }
        }
    }

    public  LocalDate getLocalDate(Option<LocalDate> option) {
        return get(option, (LocalDate)null);
    }
    public LocalDate get(Option<LocalDate> option, LocalDate defaultValue){
        return parseLocalDate(getMultipleNameOptionValue(option),get(option.name(),defaultValue));
    }
    public LocalDate get(String option, LocalDate defaultValue) {

        return parseLocalDate(get(option),defaultValue);
    }

    private LocalDate parseLocalDate(Object value,LocalDate defaultValue) {
        if (value == null) {
            return defaultValue;
        } else {
            if(value instanceof LocalDate){
                return (LocalDate)value;
            }else if(value instanceof String){
                return LocalDate.parse((String)value, DateTimeUtil.LOCAL_DATE);
            }else{
                return LocalDate.parse(value.toString(), DateTimeUtil.LOCAL_DATE);
            }
        }
    }

    public LocalTime getLocalTime(Option<LocalTime> option) {
        return get(option, (LocalTime)null);
    }

    public LocalTime get(Option<LocalTime> option, LocalTime defaultValue){
        return parseLocalTime(getMultipleNameOptionValue(option),get(option.name(),defaultValue));
    }
    public LocalTime get(String option, LocalTime defaultValue) {
        return parseLocalTime(get(option),defaultValue);
    }

    private LocalTime parseLocalTime(Object value,LocalTime defaultValue) {
        if (value == null) {
            return defaultValue;
        } else {
            if(value instanceof LocalTime){
                return (LocalTime) value;
            }else if(value instanceof String){
                return LocalTime.parse((String) value, DateTimeUtil.LOCAL_TIME);
            }else{
                return LocalTime.parse(value.toString(), DateTimeUtil.LOCAL_TIME);
            }
        }
    }

//    public <T extends Entity> T get(Option<Entity> option) {
//        Object object = options.get(option.name());
//        if (object instanceof Entity) {
//            return (T) object;
//        } else if (object instanceof Map) {
//            Entity entity = new Entity();
//            entity.rawMap((Map<String, Object>) object);
//            return (T) entity;
//        } else {
//            throw new RuntimeException("error value type:" + ((object == null) ? "[null]" : object.getClass().toString()));
//        }
//    }

    public <T extends Entity> T as(T entity) {
        Entity value = null;
        try {
            value = entity.getClass().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return (T) value.rawMap(options);

    }

//    public <T extends Entity> T get(Option<Entity> option, Entity defaultValue) {
//        return get(option.name(),defaultValue);
//    }
//
//
//    public <T extends Entity> T get(String option, Entity defaultValue) {
//        Object object = options.get(option);
//        if (object instanceof Entity) {
//            try {
//                Entity entity = defaultValue.getClass().newInstance();
//                return (T) entity.rawMap(((Entity) object).rawMap());
//            } catch (Exception e) {
//                throw new RuntimeException(e);
//            }
//        } else if (object instanceof Map) {
//            try {
//                Entity entity = defaultValue.getClass().newInstance();
//                return (T) entity.rawMap((Map<String, Object>) object);
//            } catch (Exception e) {
//                throw new RuntimeException(e);
//            }
//        } else {
//            return (T) defaultValue;
//        }
//    }


    public Byte getByte(Option<Byte> option) {
        return get(option,(Byte)null);

    }

    public Short getShort(Option<Short> option) {
        return get(option,(Short)null);
    }

    public Integer getInteger(Option<Integer> option) {
        return get(option,(Integer)null);
    }

    public Long getLong(Option<Long> option) {
        return get(option,(Long)null);
    }

    public Byte get(String option, Byte defaultValue) {
        Object value = get(option);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Byte) {
                return (Byte) value;
            } else if (value instanceof Boolean) {
                if ((Boolean) value) {
                    return 1;
                }
                {
                    return 0;
                }
            } else if (value instanceof Number) {
                return ((Number) value).byteValue();
            } else {
                return Byte.valueOf(value.toString());
            }

        }
    }

    public Integer get(String option, Integer defaultValue) {
        Object value = get(option);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Integer) {
                return (Integer) value;
            } else if (value instanceof Number) {
                return ((Number) value).intValue();
            } else {
                return Integer.valueOf(value.toString());
            }
        }
    }

    public Long get(String option, Long defaultValue) {
        Object value = get(option);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Long) {
                return (Long) value;
            } else if (value instanceof Number) {
                return ((Number) value).longValue();
            } else {
                return Long.valueOf(value.toString());
            }
        }
    }

    public Short get(String option, Short defaultValue) {
        Object value = get(option);
        if (value == null) {
            return defaultValue;
        } else {
            if (value instanceof Short) {
                return (Short) value;
            } else if (value instanceof Number) {
                return ((Number) value).shortValue();
            } else {
                return Short.valueOf(value.toString());
            }
        }
    }

    public <T> T get(String option, T defaultValue) {

        Object object = get(option);

        if (object == null) {
            return defaultValue;
        }

        if(defaultValue instanceof String){
            if(object.toString().isEmpty()){
                return defaultValue;
            }
        }

        if (defaultValue instanceof Entity) {
            if (object instanceof Entity) {
                try {
                    Entity entity = (Entity) defaultValue.getClass().newInstance();
                    return (T) entity.rawMap(((Entity) object).rawMap());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else if (object instanceof Map) {
                try {
                    Entity entity = (Entity) defaultValue.getClass().newInstance();
                    return (T) entity.rawMap((Map<String, Object>) object);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                return (T) defaultValue;
            }
        } else {

            return (T) object;
        }

    }


    public Short get(Option<Short> option, Short defaultValue) {
        Short value = null;
        if (( value = getMultipleNameOptionValue(option)) != null) {
            return value;
        }
        return get(option.name(), defaultValue);
    }

    public Integer get(Option<Integer> option, Integer defaultValue) {
        Integer value = getMultipleNameOptionValue(option);
        return (value!=null)?value:get(option.name(), defaultValue);
    }

    public Long get(Option<Long> option, Long defaultValue) {
        Long value = getMultipleNameOptionValue(option);
        return (value!=null)?value:get(option.name(), defaultValue);
    }

    public Byte get(Option<Byte> option, Byte defaultValue) {
        Byte value = getMultipleNameOptionValue(option);
        return (value!=null)?value:get(option.name(), defaultValue);
    }

    public <T> T get(Option<T> option, T defaultValue) {

        T value = getMultipleNameOptionValue(option);
        return (value!=null)?value:get(option.name(), defaultValue);
    }

    private <T> T getMultipleNameOptionValue(Option<T> option) {
        Object value;
        if (option.names() != null) {
            for (String name : option.names()) {
                value = get(name);
                if (value != null) {
                    return (T) value;
                }
            }
        }
        return null;
    }

    public <T> T remove(Option<T> option) {
        return remove(option.name());
    }

    public <T> T remove(String option) {
        if (option != null) {
            return (T) this.options.remove(option);
        } else {
            return null;
        }
    }

    public Map<String, Object> rawMap() {
        return options;
    }

    public Entity rawMap(Map<String, Object> map) {
        this.options = map;
        return this;
    }
}
